#-*- coding: utf-8 -*-

from __future__ import division
import functools
import os
import multiprocessing
import time
import sys
import ConfigParser
import signal
from pysigset import suspended_signals
from pysigset import SIG_SETMASK, sigsuspend, sigdelset, sigprocmask, SIGSET
from random import Random
from dm_py_util import INIT_LOG, DEBUG_LOG, ERROR_LOG

from engine.daemonize import Daemon
from engine.io_scheduler import IOScheduler, TimeoutCallback, PeriodicCallback
from engine.trace_back import fn
from engine import stack_context



def random_str(randomlength=16):
    chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
    random = Random()
    return ''.join([chars[random.randint(0, len(chars)-1)] for i in xrange(randomlength)])

class Event:
    Read = IOScheduler.READ
    Write = IOScheduler.WRITE
    ERROR = IOScheduler.ERROR

class BaseException(Exception):
    def __init__(self, error_name, *args):
        self._error_name = error_name
        super(BaseException, self).__init__(*args)

    def __str__(self):
        return self._error_name + ':' + super(BaseException, self).__str__()

class InitException(BaseException):
    def __init__(self, *args):
        super(InitException, self).__init__('InitError', *args)

#内部错误 不能恢复的
class InnerError(BaseException):
    def __init__(self, *args):
        super(InnerError, self).__init__('InnerError', *args)

#处理错误 重新初始化就能恢复的
class ProcessError(BaseException):
    def __init__(self, *args):
        super(ProcessError, self).__init__('ProcessError', *args)

#用户的请求错误 不影响程序执行的
class RequestError(BaseException):
    def __init__(self, *args):
        super(RequestError, self).__init__('ReuqestError', *args)



class Worker(object):
    def __init__(self, pip, config='', name=''):
        self.io = IOScheduler(self.process_idle)
        self._pip = pip
        self._config = config
        self._name = (name if name else random_str(16))
        self._process = multiprocessing.Process(target=self.__process,
                                                name=self._name)
        self.io.initialize()

    def _pip_event_callback(self, fd, event):
        if event | self.io.READ:
            if fd.fileno() == self._pip.fileno():
                 q = fd.recv()
                 if q == 'q':
                     self._uninit()
                     sys.exit(0)

    def get_io(self):
        return self.io

    def init(self):
        pass

    def start(self):
        pass

    def initialize(self):
        self.io.add_handler(self._pip, self._pip_event_callback, self.io.READ)
        self.init()

    def _process_once(self):
        self.io.process_once()

    def set_timeout(self, callback, deadline, *args, **kwargs):
        return self.io.set_timeout(callback, deadline, *args, **kwargs)

    def set_interval(self, callback, interval, *args, **kwargs):
        pc = PeriodicCallback(functools.partial(stack_context.wrap(callback), *args, **kwargs), interval, self.io)
        pc.start()
        return pc

    def remove_interval(self, period_event):
        period_event.stop()
        pc = None

    def remove_timeout(self, timeout):
        return self.io.remove_timeout(timeout)

    def process_idle(self):
        time.sleep(1)
        DEBUG_LOG('process idle.....')

    def _uninit(self):
        self.uninit()
        self.io.close(True)
        DEBUG_LOG('uninit........')

    def uninit(self):
        pass

    def __process(self):
        while 1:
            if self.io._running:
                raise RuntimeError("IOScheduler is already running")
            if self.io._stopped:
                self.io._stopped = False
                break
            self.io._running = True
            try:
                self.initialize()
            except Exception:
                ERROR_LOG('work ini failed', fn())
                raise InitException('worker init error!')
            try:
                self.start()
            except Exception:
                ERROR_LOG('start process failed!', fn())
                raise
            try:
                while 1:
                    try:
                        self._process_once()
                    except InnerError, e:
                        raise
                    except ProcessError, e:
                        self._uninit()
                        break
                    except RequestError, e:
                        continue
                    except Exception, e:
                        ERROR_LOG('multi process error', fn())
                        # print >>sys.stderr, str(e)
                        raise InnerError('unknow error')
            finally:
                self.io._stopped = False


    def _start(self):
        self._process.start()

    def add_handler(self, fd, handler, events):
        self.io.add_handler(fd, handler, events)

    def update_handler(self, fd, handler, events):
        self.io.update_handler(fd. handler, events)

    def remove_handler(self, fd):
        self.io.remove_handler(fd)

    def join(self):
        self._process.join()

    def is_running(self):
        return self._process.is_alive()

    def pid(self):
        return self._process.pid

    def name(self):
        return self._name




class Master(object):
    def __init__(self, worker=Worker, main_config=''):

        self._has_child_down = False
        self._go_to_quit = False
        self._go_to_term = False
        self._go_to_reload_workers = False
        self._send_all_quit = False
        self._worker = worker
        #master config file
        signal.signal(signal.SIGCLD, self._sig_child_handler)
        signal.signal(signal.SIGQUIT, self._sig_quit_handler)
        signal.signal(signal.SIGTERM, self._sig_term_handler)
        signal.signal(signal.SIGUSR1, self._sig_usr1_handler)

        self._master_config = ''
        self._worker_config = ''
        self._worker_num = 0
        self._force_kill_timeout = 0
        self._pid_file = ''
        self._log_config = ''
        self._error_log = ''

        config_parser = ConfigParser.ConfigParser()
        try:
            with open(main_config, 'rw') as f:
                config_parser.readfp(f)
                self._worker_num = config_parser.has_option('server', 'worker_num') and \
                        config_parser.getint('server', 'worker_num')
                self._force_kill_timeout = config_parser.has_option('server', 'force_kill_timeout') and \
                        config_parser.getint('server', 'force_kill_timeout')
                self._pid_file = config_parser.has_option('server', 'pid_file') and \
                        config_parser.get('server', 'pid_file')
                self._error_log=  config_parser.has_option('server', 'error_log') and \
                        config_parser.get('server', 'error_log')
                self._log_config = config_parser.has_option('server', 'log_config') and \
                        config_parser.get('server', 'log_config')
                self._master_config = config_parser.has_option('server', 'master_config') and \
                        config_parser.get('server', 'master_config')
                self._worker_config = config_parser.has_option('server', 'worker_config') and \
                        config_parser.get('server', 'worker_config')
        except:
            pass
        finally:
            self._worker_num = self._worker_num or 1
            self._force_kill_timeout = self._force_kill_timeout or 3
            self._log_config = self._log_config or ''
            self._master_config = self._master_config or ''
            self._worker_config = self._worker_config or ''

        #on quit, force to quit timeout
        self._workers = []
        self._pips = []
        self._daemon = Daemon(stderr=self._error_log, pidfile=self._pid_file)

    def _sig_child_handler(self, signum, frame):
        self._has_child_down = True

    def _sig_quit_handler(self, signum, frame):
        self._go_to_quit = True

    def _sig_term_handler(self, signum, frame):
        self._go_to_term = True

    def _sig_usr1_handler(self, signum, frame):
        self._go_to_reload_workers = True

    def init(self):
        pass

    def serve_forever(self):
        try:
            INIT_LOG(self._log_config)
        except Exception, e:
            print >>sys.stderr, 'init log error %s\n' % self._log_config, e
            raise
        try:
            self.init()
        except:
            print >>sys.stderr, 'master init error %s\n' % self._master_config
            raise
            # raise InitException('master init error %s\n' % self._master_config)


        self._daemon.run()

        old_sig_set = SIGSET()
        sigprocmask(SIG_SETMASK, 0, old_sig_set)
        sigdelset(old_sig_set, signal.SIGCLD)
        sigdelset(old_sig_set, signal.SIGQUIT)
        with suspended_signals(signal.SIGCLD, signal.SIGQUIT):
            self._create_workers()
            while 1:
                try:
                    sigsuspend(old_sig_set)
                except:
                    continue
                else:
                    if (self._go_to_term or self._go_to_quit) and not self._send_all_quit:
                        self._order_all_quit()
                        self._send_all_quit = True
                        if self._go_to_quit:
                            continue
                        else:
                            time.sleep(1)

                    if self._go_to_quit or self._go_to_term:
                        self._process_quit_signal(self._force_kill_timeout)
                        if self._go_to_term:
                            break
                        else:
                            print >>sys.stderr, 'self.workers num %d', len(self._workers)
                            if not len(self._workers):
                                break
                    elif self._go_to_reload_workers:
                        self._order_all_quit()
                        self._go_to_reload_workers = False
                    elif self._has_child_down:
                        self._restart_works()
                    else:
                        continue
        self._daemon.exit()


    def _order_all_quit(self):
        for pip in self._pips:
            pip.send('q')

    def _create_workers(self):
        for i in xrange(self._worker_num):
            pip0, pip1 = multiprocessing.Pipe()
            worker = apply(self._worker, (pip1, self._worker_config, str(i)))
            self._workers.append(worker)
            self._pips.append(pip0)
            worker._start()

    def _process_quit_signal(self, timeout):
        wait_to_quit_time = (
            1 if
            self._go_to_quit else
            self._force_kill_timeout)
        for i in xrange(wait_to_quit_time):
            for worker in self._workers:
                if not worker.is_running():
                    worker.join()
                    self._workers.remove(worker)
            if len(self._workers) and self._go_to_term:
                time.sleep(1)
            else:
                break
        #force kill
        if self._go_to_term:
            if len(self._workers):
                for worker in self._workers:
                    if worker.is_running():
                        os.kill(worker.pid(), 9)
                time.sleep(1)
                for worker in self._workers:
                    worker.join()


    def _restart_works(self):
        for i in xrange(len(self._workers)):
            if not self._workers[i].is_running():
                self._workers[i].join()
                pip0, pip1 = multiprocessing.Pipe()
                worker = apply(self._worker, (pip1, self._worker_config,  str(i)))
                self._workers[i] = worker
                self._pips[i] = pip0
                worker._start()
        self._has_child_down = False



if __name__ == '__main__':
    class TestWorker(Worker):
        def init(self):
            DEBUG_LOG('TestWorker init.......')
        def start(self):
            DEBUG_LOG('TestWorker start......')
            self.set_interval(self.interval_event, 1000, 2, 3, 4, a=2, b=3)
            self.set_timeout(self.timeout_event, 10000, 2, 3, 4, a=2, b=3)
        def interval_event(self,*args, **kw):
            DEBUG_LOG('interval event', args, kw)
        def timeout_event(self, *args, **kw):
            DEBUG_LOG('timeout event', args, kw)
    master = Master(main_config='../conf/master.config.init', worker=TestWorker)
    master.serve_forever()
